Lightning Timer
https://gyazo.com/e323347d18fb6750ac01d1531efd7897
指定したURLプリフィクスを持つページでリロードするとタイマーを表示します
「スタート」をクリックすると、カウントダウンが始まります
3分後にDOUBLE_BELL_URLに記載の音が鳴ります
2分30秒後(残り30秒)のタイミングでSINGLE_BELL_URLの音が鳴ります
「ストップ」をクリックすると、0秒にリセットします
同じプロジェクトであればページを遷移してもタイマーが継続します
https://gyazo.com/f71d25632f2615ba0ba7dc45dfe8ac0e
タイマー稼働中に「スタート」をクリックすると、3分を計り直します
早めに話終わった人がいた時に、次の人の発表を開始するのに便利です
https://gyazo.com/7994400b8c961553fde24910b77fbf28
残り30秒で黄色い表示になり、残り0秒を超過すると赤い表示でカウントダウンし続けます
残り時間が無くなっても話し続けている人が、どれぐらい話し続けているかを意識してもらうためです
https://gyazo.com/5a20148d3ce79a4d6a3ab5297f05de81
右下の「🛎」をクリックすると、ベルを1回鳴らせます
サウンドテストに便利です
スクリーン共有後、音声がリモート先の人に届いてるか確認してください
大幅に時間が超過しても喋り続ける人がいたら連打してください
https://gyazo.com/630fdb924da91b757a5e968ef89145a7
SINGLE_BELL_URL
DOUBLE_BELL_URL
SINGLE_BELL_URL
https://scrapbox.io/files/6287a062dad5c50022aadae8.mp3
DOUBLE_BELL_URL
https://scrapbox.io/files/6287a069f289c7001d4c0e5d.mp3
※必ず配布元の利用規約をよく読み、配布元のサイトより効果音ファイルをダウンロードしてください
code:script.js
/*
Lightning Timer © akiroom
*/
// 本タイマーをセットするURL(先頭一致で判定します)
// 何分間を計測するか(開始何分後に2回鳴らすか)
const GOAL_MINUTES = 3 // 3分
// 終了何分前に1回鳴らすか
const PRE_GOAL_MINUTES = .5 // 30秒
function leftFillNum(num, targetLength) {
return num.toString().padStart(targetLength, 0);
}
function getTimerLabelString(targetTime) {
let prefix = ''
let minutes, secs, milliSecs
if (targetTime >= 0) {
minutes = Math.floor(targetTime / 1000 / 60)
secs = Math.floor((targetTime - minutes * 60 * 1000) / 1000)
milliSecs = targetTime % 1000
} else {
prefix = '-'
minutes = Math.abs(Math.floor(targetTime / 1000 / 60) + 1)
secs = Math.floor((minutes * 60 * 1000 - targetTime) / 1000)
milliSecs = Math.abs(targetTime % 1000)
}
return ${prefix}${leftFillNum(minutes, 2)}:${leftFillNum(secs, 2)}.${leftFillNum(milliSecs, 3)}
}
const TIMER_STAGE_ID = 'scrapbox-timer-stage'
const style = `
position: fixed;
right: 0;
bottom: 40px;
padding: 16px;
border-radius: 8px 0 0 8px;
z-index: 999;
background: rgba(0,0,0,.9);
}
background: rgba(255,255,255,.2);
border: 2px solid rgba(255,255,255,.8);
}
text-align: right;
font-size: 32px;
}
}
color: red;
}
`
const isTargetPage = location.href.toLowerCase().startsWith(LOADABLE_URL_PREFIX.toLowerCase())
let timerStage = document.getElementById(TIMER_STAGE_ID)
if (!timerStage && isTargetPage) {
document.head.insertAdjacentHTML('beforeend', <style type="text/css">${style}</style>)
document.body.insertAdjacentHTML('beforeend', <div id="${TIMER_STAGE_ID}"><div class="timer-status-label">00:00</div><div><button class="start-button">スタート</button><button class="stop-button">ストップ</button><button class="sound-test-button">🛎</button></div></div>)
const startButton = document.querySelector(#${TIMER_STAGE_ID} .start-button)
const stopButton = document.querySelector(#${TIMER_STAGE_ID} .stop-button)
const soundTestButton = document.querySelector(#${TIMER_STAGE_ID} .sound-test-button)
const timerStatusLabel = document.querySelector(#${TIMER_STAGE_ID} .timer-status-label)
timerStatusLabel.innerText = getTimerLabelString(0)
const singleBellAudio = new Audio(SINGLE_BELL_URL)
const doubleBellAudio = new Audio(DOUBLE_BELL_URL)
const goalTime = 1000 * 60 * GOAL_MINUTES
const preGoalTime = 1000 * 60 * PRE_GOAL_MINUTES
let startDate = null
let currentTimer = null
let singleBellPlayed = false
let doubleBellPlayed = false
function resetTimer () {
if (currentTimer) {
clearInterval(currentTimer)
}
timerStatusLabel.innerText = getTimerLabelString(0)
singleBellAudio.pause()
singleBellAudio.currentTime = 0
doubleBellAudio.pause()
doubleBellAudio.currentTime = 0
timerStatusLabel.classList.remove('warn')
timerStatusLabel.classList.remove('minus')
}
// スタートボタンを押した時の処理
startButton.addEventListener('click', (e) => {
e.stopPropagation()
resetTimer()
startDate = new Date()
singleBellPlayed = false
doubleBellPlayed = false
currentTimer = setInterval(() => {
const elapsedTime = new Date() - startDate
const statusTime = goalTime - elapsedTime
timerStatusLabel.innerText = getTimerLabelString(statusTime)
if (statusTime < preGoalTime) {
if (!singleBellPlayed) {
singleBellPlayed = true
singleBellAudio.pause()
singleBellAudio.currentTime = 0
singleBellAudio.play()
timerStatusLabel.classList.add('warn')
}
} else {
timerStatusLabel.classList.remove('warn')
}
if (statusTime >= 0) {
timerStatusLabel.classList.remove('minus')
} else {
if (!doubleBellPlayed) {
doubleBellPlayed = true
doubleBellAudio.pause()
doubleBellAudio.currentTime = 0
doubleBellAudio.play()
}
timerStatusLabel.classList.add('minus')
}
}, 10)
})
// ストップボタンを押した時の処理
stopButton.addEventListener('click', (e) => {
e.stopPropagation()
resetTimer()
})
// サウンドテストボタンを押した時の処理
soundTestButton.addEventListener('click', (e) => {
e.stopPropagation()
singleBellAudio.pause()
singleBellAudio.currentTime = 0
singleBellAudio.play()
})
}